//
// Created by LiuYou on 2021/8/7.
//

#include <cstdio>
#include <cstdlib>
#include <glob.h>
#include <sys/syslog.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include "MediaLib.hpp"
#include "TBF.hpp"
#include "../include/Protocol.hpp"
#include "Server.hpp"


//extern NetRadio::Server::ServerConfig NetRadio::Server::serverConfig;
//NetRadio::Server::ServerConfig NetRadio::Server::serverConfig;




static int MP3_BIT_RATE = 64 * 1024;


namespace NetRadio {
    namespace Server {
        // TODO 注意: !!! 请看 Server.cpp 中的最后的错误的注释！
//        extern struct ServerConfig serverConfig;

//        using namespace MediaLib;
        namespace MediaLib {
            using NetRadioTBF::TBF;

            static const int PATH_SIZE = 1024;
            static const int LINE_BUF_SIZE = 1024;


            struct ChannelContext {
                ChannelId channelId;
                char* description;
                glob_t mp3Glob;
                int pos;
                int fd;
                off_t offset;
                TBF* tbf;

            };

            static struct ChannelContext channelContext[NetRadio::MAX_CHANNEL_ID + 1];


            static struct ChannelContext* pathToEntry(char* path) {
                // path/aesc.text       path/*.mp3
                char pathString[PATH_SIZE]{};
                char lineBuf[LINE_BUF_SIZE]{};

                static ChannelId currentId = MIN_CHANNEL_ID;

                strncpy( pathString, path, PATH_SIZE );
                strncat( pathString, "/desc.text", PATH_SIZE );

                FILE* fp = fopen( pathString, "r" );
                if (fp == nullptr) {
                    syslog( LOG_INFO, "%s is not a channel dir(Can't find desc.text)", path );
                    return nullptr;
                }

                if (fgets( lineBuf, LINE_BUF_SIZE, fp ) == nullptr) {
                    syslog( LOG_INFO, "%s is not a channel dir(Can't get the desc.text)", path );
                    fclose( fp );
                    return nullptr;
                }

                struct ChannelContext* me;
                void* pVoid = malloc( sizeof( *me ));
                me = static_cast<ChannelContext*>(pVoid);
                if (me == nullptr) {
                    syslog( LOG_ERR, "malloc(): %s", strerror(errno));
                    return nullptr;
                }

                me->tbf = NetRadio::Server::NetRadioTBF::
                tbfInit( MP3_BIT_RATE / 8, MP3_BIT_RATE / 8 * 10 );
                if (me->tbf == nullptr) {
                    syslog( LOG_ERR, "tbfInit(): %s", strerror(errno));
                    free( me );
                    return nullptr;
                }

                me->description = strdup( lineBuf );

                strncpy( pathString, path, PATH_SIZE );
                strncat( pathString, "/*.mp3", PATH_SIZE );

                if (glob( pathString, 0, nullptr, &me->mp3Glob ) != 0) {
                    ++currentId;
                    syslog( LOG_ERR, "%s is not a channel dir(Can't find mp3 file)",
                            path );
                    free( me );
                    return nullptr;
                }

                me->pos = 0;
                me->offset = 0;
                me->fd = open( me->mp3Glob.gl_pathv[me->pos], O_RDONLY );
                if (me->fd < 0) {
                    syslog( LOG_WARNING, "%s open failed.", me->mp3Glob.gl_pathv[me->pos] );
                    free( me );
                    return nullptr;
                }


                me->channelId = currentId;
                ++currentId;

                return me;

            }

//            int getChannelList(struct MediaLibEntry** result, int* resnum) {
//                /*variable*/
//                int num = 0;
//                int i = 0;
//                char path[PATH_SIZE];
//                glob_t globres;
//                struct MediaLibEntry* ptr;
//                struct ChannelContext* res;
//
//                for (int i = 0; i < MAX_CHANNEL_ID; i++) {
//                    channelContext[i].channelId = -1;
//                }
//                snprintf( path, PATH_SIZE, "%s/*", serverConfig.mediaDir );
//#ifdef DEBUG
//                printf("current path:%s\n", path);
//#endif
//                if (glob( path, 0, NULL, &globres )) {
//                    return -1;
//                }
//#ifdef DEBUG
//                printf("globres.gl_pathv[0]:%s\n", globres.gl_pathv[0]);
//        printf("globres.gl_pathv[1]:%s\n", globres.gl_pathv[1]);
//        printf("globres.gl_pathv[2]:%s\n", globres.gl_pathv[2]);
//#endif
//                ptr = static_cast<struct MediaLibEntry*>(malloc(
//                        sizeof( struct MediaLibEntry ) * globres.gl_pathc ));
//                if (ptr == NULL) {
//                    syslog( LOG_ERR, "malloc() error." );
//                    exit( 1 );
//                }
//                for (i = 0; i < globres.gl_pathc; i++) {
//                    //globres.gl_pathv[i] -->"/var/media/ch1"
//                    res = static_cast<struct ChannelContext*>(pathToEntry(
//                            globres.gl_pathv[i] ));//path-->record
//                    if (res != NULL) {
//                        syslog( LOG_ERR, "path2entry() return : %d %s.", res->channelId, res->description );
//                        memcpy( channelContext + res->channelId, res, sizeof( *res ));
//                        ptr[num].channelId = res->channelId;
//                        ptr[num].description = strdup( res->description );
//                        num++;
//                    }
//                }
//                *result = static_cast<MediaLibEntry*>(realloc( ptr, sizeof( struct MediaLibEntry ) * num ));
//                if (*result == NULL) {
//                    syslog( LOG_ERR, "realloc() failed." );
//                }
//                *resnum = num;
//                return 0;
//
//
//            }


            // TODO 注意: @_@ 这里二级指针的传参不懂。
            int getChannelList(struct MediaLibEntry** pStructMediaLibEntry, int* num) {
                for (int i = 0; i < NetRadio::MAX_CHANNEL_ID + 1; ++i) {
                    channelContext[i].channelId = -1;
                }

                char path[PATH_SIZE]{};
                snprintf( path, PATH_SIZE, "%s/*", serverConfig.mediaDir );

                glob_t globResult;
                if (glob( path, 0, nullptr, &globResult )) {
                    return -1;
                }

                struct MediaLibEntry* ptr;
                void* pVoid = malloc( sizeof( struct MediaLibEntry ) * globResult.gl_pathc );
                ptr = static_cast<MediaLibEntry*>(pVoid);
                if (ptr == nullptr) {
                    syslog( LOG_ERR, "malloc() error." );
                    exit( 1 );
                }

                int index = 0;
                for (int i = 0; i < globResult.gl_pathc; ++i) {
                    // globResult.gl_pathv[i] -> "/var/media/ch1"
                    ChannelContext* res = pathToEntry( globResult.gl_pathv[i] );
                    if (res != nullptr) {
                        syslog( LOG_DEBUG, "pathToEntry() returned: %d %s.",
                                res->channelId, res->description );
                        memcpy( channelContext + res->channelId, res, sizeof( *res ));
                        ptr[index].channelId = res->channelId;
                        ptr[index].description = strdup( res->description );
                        ++index;
                    }
                }

                void* pVoid1 = realloc( ptr, sizeof( struct MediaLibEntry ) * index );
                *pStructMediaLibEntry = static_cast<MediaLibEntry*>(pVoid1);
                if (*pStructMediaLibEntry == nullptr) {
                    syslog( LOG_ERR, "realloc() failed." );
                }

                *num = index;

                return 0;
            }

            int freeChannelList(struct MediaLibEntry* ptrMediaLibEntry) {
                free( ptrMediaLibEntry );
                return 0;
            }

            static int openNext(ChannelId channelId) {
                for (int i = 0; i < channelContext[channelId].mp3Glob.gl_pathc; ++i) {
                    ++channelContext[channelId].pos;
                    if (channelContext[channelId].pos == channelContext[channelId].mp3Glob.gl_pathc) {
                        channelContext[channelId].pos = 0;
                        break;
                    }
                    close( channelContext[channelId].fd );
                    channelContext[channelId].fd =
                            open( channelContext[channelId].mp3Glob.gl_pathv[channelContext[channelId].pos], O_RDONLY );
                    if (channelContext[channelId].fd < 0) {
                        syslog( LOG_WARNING, "open(%s): %s",
                                channelContext[channelId].mp3Glob.gl_pathv[channelContext[channelId].pos],
                                strerror(errno));
                    } else {
                        channelContext[channelId].offset = 0;
                        return 0;
                    }
                }
                syslog( LOG_ERR, "None of mp3 in channel %d is available.",
                        channelId );
            }

            int readChannelList(ChannelId channelId, void* buf, std::size_t size) {
                NetRadio::Server::NetRadioTBF::
                tbfFetchToken( channelContext[channelId].tbf, size );

                size_t tbfSize = 0;
                ssize_t len;
                while (true) {
                    len = pread( channelContext[channelId].fd, buf, tbfSize, channelContext[channelId].offset );
                    if (len < 0) {
                        syslog( LOG_WARNING, "meida file %s pread(): %s",
                                channelContext[channelId].mp3Glob.gl_pathv[channelContext[channelId].pos],
                                strerror(errno));
                        openNext( channelId );
                        // 不下面使用的原因是逻辑有点复杂。
//                        if (openNext( channelId ) < 0) {
//                            syslog( LOG_ERR, "channel %d: There is no succeed open.",
//                                    channelId );
//                        }
                    } else if (len == 0) {
                        syslog( LOG_DEBUG, "media file %s is over.",
                                channelContext[channelId].mp3Glob.gl_pathv[channelContext[channelId].pos] );
                        openNext( channelId );
                        // 不下面使用的原因是逻辑有点复杂。
//                        if (openNext( channelId ) < 0) {
//                            syslog( LOG_ERR, "channel: %d: There is no succeed open.",
//                                    channelId );
//                        }
                    } else {
                        channelContext[channelId].offset += len;
                        break;
                    }
                }
                if (tbfSize - len > 0) {
                    NetRadio::Server::NetRadioTBF::
                    tbfReturnToken( channelContext[channelId].tbf,
                                    tbfSize - len );
                }

                return len;
            }

        }
    }

}
